package evl

import (
	"testing"

	"gitcode.com/deyiyangyang/bampoo/ast"
	"gitcode.com/deyiyangyang/bampoo/obj"
)

// TestBeginWithMultipleExpressions tests begin with multiple expressions
func TestBeginWithMultipleExpressions(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// (begin (define x 10) (define y 20) (+ x y))
	begin := &ast.BeginExpr{
		Body: []ast.Expression{
			&ast.DefineExpr{
				Name:  ast.Symbol{Value: "x"},
				Value: &ast.IntegerLiteral{Value: 10},
			},
			&ast.DefineExpr{
				Name:  ast.Symbol{Value: "y"},
				Value: &ast.IntegerLiteral{Value: 20},
			},
			&ast.ArithmeticExpr{
				Operator: "+",
				Operands: []ast.Expression{
					&ast.Symbol{Value: "x"},
					&ast.Symbol{Value: "y"},
				},
			},
		},
	}

	result, err := evaluator.Eval(begin)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 30 {
		t.Errorf("expected 30, got %d", intVal.Value)
	}

	// Verify that variables were defined in the environment
	xVal, ok := env.Get("x")
	if !ok {
		t.Error("expected x to be defined in environment")
	}

	yVal, ok := env.Get("y")
	if !ok {
		t.Error("expected y to be defined in environment")
	}

	xIntVal, ok := xVal.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue for x, got %T", xVal)
	}

	yIntVal, ok := yVal.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue for y, got %T", yVal)
	}

	if xIntVal.Value != 10 {
		t.Errorf("expected x to be 10, got %d", xIntVal.Value)
	}

	if yIntVal.Value != 20 {
		t.Errorf("expected y to be 20, got %d", yIntVal.Value)
	}
}

// TestBeginWithSingleExpression tests begin with a single expression
func TestBeginWithSingleExpression(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// (begin (+ 1 2 3))
	begin := &ast.BeginExpr{
		Body: []ast.Expression{
			&ast.ArithmeticExpr{
				Operator: "+",
				Operands: []ast.Expression{
					&ast.IntegerLiteral{Value: 1},
					&ast.IntegerLiteral{Value: 2},
					&ast.IntegerLiteral{Value: 3},
				},
			},
		},
	}

	result, err := evaluator.Eval(begin)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 6 {
		t.Errorf("expected 6, got %d", intVal.Value)
	}
}

// TestBeginWithStringLiterals tests begin with string literals
func TestBeginWithStringLiterals(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// (begin "hello" "world")
	begin := &ast.BeginExpr{
		Body: []ast.Expression{
			&ast.StringLiteral{Value: "hello"},
			&ast.StringLiteral{Value: "world"},
		},
	}

	result, err := evaluator.Eval(begin)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	strVal, ok := result.(*obj.StringValue)
	if !ok {
		t.Fatalf("expected StringValue, got %T", result)
	}

	if strVal.Value != "world" {
		t.Errorf("expected 'world', got '%s'", strVal.Value)
	}
}

// TestBeginWithMixedTypes tests begin with mixed expression types
func TestBeginWithMixedTypes(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// (begin (define x 5) "intermediate" (+ x 10))
	begin := &ast.BeginExpr{
		Body: []ast.Expression{
			&ast.DefineExpr{
				Name:  ast.Symbol{Value: "x"},
				Value: &ast.IntegerLiteral{Value: 5},
			},
			&ast.StringLiteral{Value: "intermediate"},
			&ast.ArithmeticExpr{
				Operator: "+",
				Operands: []ast.Expression{
					&ast.Symbol{Value: "x"},
					&ast.IntegerLiteral{Value: 10},
				},
			},
		},
	}

	result, err := evaluator.Eval(begin)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 15 {
		t.Errorf("expected 15, got %d", intVal.Value)
	}

	// Verify that x was defined
	xVal, ok := env.Get("x")
	if !ok {
		t.Error("expected x to be defined in environment")
	}

	xIntVal, ok := xVal.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue for x, got %T", xVal)
	}

	if xIntVal.Value != 5 {
		t.Errorf("expected x to be 5, got %d", xIntVal.Value)
	}
}

// TestBeginWithFunctionCalls tests begin with function calls
func TestBeginWithFunctionCalls(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// First define a function
	define := &ast.DefineExpr{
		Name: ast.Symbol{Value: "double"},
		Value: &ast.LambdaExpr{
			Parameters: []ast.Symbol{{Value: "n"}},
			Body: []ast.Expression{
				&ast.ArithmeticExpr{
					Operator: "*",
					Operands: []ast.Expression{
						&ast.Symbol{Value: "n"},
						&ast.IntegerLiteral{Value: 2},
					},
				},
			},
		},
	}

	_, err := evaluator.Eval(define)
	if err != nil {
		t.Fatalf("unexpected error defining function: %v", err)
	}

	// Now test begin with function calls
	// (begin (define x 7) (double x))
	begin := &ast.BeginExpr{
		Body: []ast.Expression{
			&ast.DefineExpr{
				Name:  ast.Symbol{Value: "x"},
				Value: &ast.IntegerLiteral{Value: 7},
			},
			&ast.List{
				Elements: []ast.Expression{
					&ast.Symbol{Value: "double"},
					&ast.Symbol{Value: "x"},
				},
			},
		},
	}

	result, err := evaluator.Eval(begin)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 14 {
		t.Errorf("expected 14, got %d", intVal.Value)
	}
}

// TestBeginWithEmptyBody tests begin with empty body (should return nil)
func TestBeginWithEmptyBody(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// (begin)
	begin := &ast.BeginExpr{
		Body: []ast.Expression{},
	}

	result, err := evaluator.Eval(begin)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if _, ok := result.(*obj.NilValue); !ok {
		t.Fatalf("expected NilValue, got %T", result)
	}
}

// TestBeginWithErrorInMiddle tests begin with an error in the middle
func TestBeginWithErrorInMiddle(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// (begin (define x 10) undefined-symbol (+ x 5))
	begin := &ast.BeginExpr{
		Body: []ast.Expression{
			&ast.DefineExpr{
				Name:  ast.Symbol{Value: "x"},
				Value: &ast.IntegerLiteral{Value: 10},
			},
			&ast.Symbol{Value: "undefined-symbol"},
			&ast.ArithmeticExpr{
				Operator: "+",
				Operands: []ast.Expression{
					&ast.Symbol{Value: "x"},
					&ast.IntegerLiteral{Value: 5},
				},
			},
		},
	}

	_, err := evaluator.Eval(begin)
	if err == nil {
		t.Fatal("expected error for undefined symbol")
	}

	if !containsSubstring(err.Error(), "undefined symbol: undefined-symbol") {
		t.Errorf("expected error message to contain 'undefined symbol: undefined-symbol', got '%s'", err.Error())
	}

	// Verify that x was still defined despite the error
	xVal, ok := env.Get("x")
	if !ok {
		t.Error("expected x to be defined in environment despite error")
	}

	xIntVal, ok := xVal.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue for x, got %T", xVal)
	}

	if xIntVal.Value != 10 {
		t.Errorf("expected x to be 10, got %d", xIntVal.Value)
	}
}

// TestBeginWithNestedBegin tests nested begin expressions
func TestBeginWithNestedBegin(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// (begin (define x 5) (begin (define y 10) (+ x y)))
	begin := &ast.BeginExpr{
		Body: []ast.Expression{
			&ast.DefineExpr{
				Name:  ast.Symbol{Value: "x"},
				Value: &ast.IntegerLiteral{Value: 5},
			},
			&ast.BeginExpr{
				Body: []ast.Expression{
					&ast.DefineExpr{
						Name:  ast.Symbol{Value: "y"},
						Value: &ast.IntegerLiteral{Value: 10},
					},
					&ast.ArithmeticExpr{
						Operator: "+",
						Operands: []ast.Expression{
							&ast.Symbol{Value: "x"},
							&ast.Symbol{Value: "y"},
						},
					},
				},
			},
		},
	}

	result, err := evaluator.Eval(begin)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 15 {
		t.Errorf("expected 15, got %d", intVal.Value)
	}

	// Verify that both x and y were defined
	xVal, ok := env.Get("x")
	if !ok {
		t.Error("expected x to be defined in environment")
	}

	yVal, ok := env.Get("y")
	if !ok {
		t.Error("expected y to be defined in environment")
	}

	xIntVal, ok := xVal.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue for x, got %T", xVal)
	}

	yIntVal, ok := yVal.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue for y, got %T", yVal)
	}

	if xIntVal.Value != 5 {
		t.Errorf("expected x to be 5, got %d", xIntVal.Value)
	}

	if yIntVal.Value != 10 {
		t.Errorf("expected y to be 10, got %d", yIntVal.Value)
	}
}

// Helper function to check if a string contains a substring
func containsSubstring(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
